/**
 * Main.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2011 Roslan Amir. All rights reserved.
 */

package com.roslan.games.moo3d;

import com.ardor3d.extension.ui.UIComponent;
import com.ardor3d.extension.ui.UIHud;
import com.ardor3d.framework.Canvas;
import com.ardor3d.image.Texture;
import com.ardor3d.input.Key;
import com.ardor3d.input.MouseState;
import com.ardor3d.input.control.OrbitCamControl;
import com.ardor3d.input.logical.InputTrigger;
import com.ardor3d.input.logical.KeyPressedCondition;
import com.ardor3d.input.logical.MouseMovedCondition;
import com.ardor3d.input.logical.TriggerAction;
import com.ardor3d.input.logical.TwoInputStates;
import com.ardor3d.intersection.PickData;
import com.ardor3d.intersection.Pickable;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Matrix3;
import com.ardor3d.math.Vector3;
import com.ardor3d.renderer.Camera;
import com.ardor3d.renderer.Renderer;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.Node;
import com.ardor3d.scenegraph.extension.Skybox;
import com.ardor3d.scenegraph.shape.Quad;
import com.ardor3d.scenegraph.shape.Sphere;
import com.ardor3d.util.ReadOnlyTimer;
import com.ardor3d.util.TextureManager;
import com.roslan.games.moo3d.GameData.Actions;
import com.roslan.games.moo3d.GameData.GameStates;
import com.roslan.games.moo3d.spatial.FleetBannerBillboard;
import com.roslan.games.moo3d.spatial.FleetNode;
import com.roslan.games.moo3d.spatial.StarNode;

/**
 * The main class. This class extends <code>Base</code> and controls the setup of Ardor3D stuff.
 * 
 * @author Roslan Amir
 * @version 1.0 - Dec 16, 2010
 */
public class Main extends Base {

	/**
	 * 
	 */
	private static final String IMAGE_BACKGROUND_TEXTURE = "images/starfield3.jpg";

	/**
	 * Reference to the single instance of the <code>GameData</code> class.
	 */
	private static final GameData gameData = GameData.getInstance();

	/**
	 * Reference to the single instance of the <code>GameManager</code> class.
	 */
	private static final GameManager gameManager = GameManager.getInstance();

	/**
	 * The instance of the UIHud class
	 */
	public static UIHud hud;

	/**
	 * Our orbiter control.
	 */
	public static OrbitCamControl control;

	/**
	 * The camera being used.
	 */
	public static Camera camera;

	/**
	 * 
	 */
	private Skybox skybox;

	//====================
	// Working variables
	//====================

	/**
	 * Rotation matrix for the spinning Star.
	 */
	private final Matrix3 rotate1 = new Matrix3();

	/**
	 * Rotation matrix for the spinning Fleet.
	 */
	private final Matrix3 rotate2 = new Matrix3();

	/**
	 * Angle of rotation for the Star sphere.
	 */
	private double angle1 = 0.0;

	/**
	 * Angle of rotation for the Fleet highlight.
	 */
	private double angle2 = 0.0;

	/**
	 * Start the program.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		start(Main.class);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ardor3d.example.ExampleBase#registerInputTriggers()
	 */
	@Override
	protected void registerInputTriggers() {
		super.registerInputTriggers();

		/*
		 * Add Strategy handler - set it up to control the main camera.
		 */
		addCameraHandler();

		/*
		 * Register the "mouse moved" for actions.
		 */
		registerMouseMove();

		/*
		 * Register the key strokes used by this program.
		 */
		registerKeys();

	}

	/**
	 * Add Strategy handler - set it up to control the main camera.
	 */
	private void addCameraHandler() {
		control = new OrbitCamControl(_canvas.getCanvasRenderer().getCamera(), Vector3.ZERO);
//		control = new OrbitCamControl(camera, Vector3.ZERO); // camera not initialized yet
		control.setupMouseTriggers(_logicalLayer, true);
		control.setSphereCoords(20.0, 90 * MathUtils.DEG_TO_RAD, 0.0);
		control.setMaxAscent(-1.0);
		control.setMinAscent(-1.0);
		control.setMaxZoomDistance(50.0);
		control.setMinZoomDistance(5.0);
		control.setZoomSpeed(0.005);
		control.setBaseDistance(5.0);
	}

	/**
	 * Register the "mouse moved" for actions.
	 */
	private void registerMouseMove() {
		_logicalLayer.registerTrigger(new InputTrigger(new MouseMovedCondition(), new TriggerAction() {
			public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
				if (gameData.gameState == GameStates.IN_STAR_INFO) {
					final MouseState mouse = inputStates.getCurrent().getMouseState();
					int x = mouse.getX();
					int y = mouse.getY();
					int dx = mouse.getDx();
					int dy = mouse.getDy();
					if (x <= 10 && dx < 0) {
						logger.info("mouse moved left");
						gameData.action = Actions.CAM_LEFT;
					} else if (x >= GameData.screenWidth - 10 && dx > 0) {
						logger.info("mouse moved right");
						gameData.action = Actions.CAM_RIGHT;
					} else if (y <= 10 && dy < 0) {
						logger.info("mouse moved down");
						gameData.action = Actions.CAM_DOWN;
					} else if (y >= GameData.screenHeight - 10 && dy > 0) {
						logger.info("mouse moved up");
						gameData.action = Actions.CAM_UP;
					}
				}
			}
		}));
	}

	/**
	 * Register key strokes for actions.
	 */
	private void registerKeys() {
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.C), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = (gameData.gameState == GameStates.IN_STAR_INFO) ? Actions.CENTER_ON_STAR : Actions.CENTER_ON_FLEET;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.G), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.TOGGLE_GRID;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F2), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.SHOW_NEXT_COLONY;
				gameData.gameState = GameStates.IN_STAR_INFO;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F3), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.SHOW_PREV_COLONY;
				gameData.gameState = GameStates.IN_STAR_INFO;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F4), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.SHOW_NEXT_FLEET;
				gameData.gameState = GameStates.IN_FLEET_INFO;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F5), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.SHOW_PREV_FLEET;
				gameData.gameState = GameStates.IN_FLEET_INFO;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F6), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.SHOW_NEXT_STAR;
				gameData.gameState = GameStates.IN_STAR_INFO;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F7), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.SHOW_PREV_STAR;
				gameData.gameState = GameStates.IN_STAR_INFO;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F12), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				gameData.action = Actions.START_COLONY_PRODUCTION;
				gameData.gameState = GameStates.IN_COLONY_PRODUCTION;
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.UP), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameData.gameState == GameStates.IN_STAR_INFO) {
					gameData.action = Actions.CAM_UP;
				}
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.LEFT), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameData.gameState == GameStates.IN_STAR_INFO) {
					gameData.action = Actions.CAM_LEFT;
				}
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.RIGHT), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameData.gameState == GameStates.IN_STAR_INFO) {
					gameData.action = Actions.CAM_RIGHT;
				}
			}
		}));
		_logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.DOWN), new TriggerAction() {
			public void perform(Canvas source, final TwoInputStates inputState, final double tpf) {
				if (gameData.gameState == GameStates.IN_STAR_INFO) {
					gameData.action = Actions.CAM_DOWN;
				}
			}
		}));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ardor3d.example.ExampleBase#initGame()
	 */
	@Override
	protected void initGame() {
		_canvas.setTitle("Master of Orion 3D (Java - Ardor3D)");
		_canvas.setVSyncEnabled(true);
		_lightState.setEnabled(true);

		/*
		 * Set the background star field
		 */
		final Node backgroundNode = new Node("BackgroundNode");
		skybox = new Skybox("Skybox", 500, 500, 500);
		final Texture texture = TextureManager.load(IMAGE_BACKGROUND_TEXTURE, Texture.MinificationFilter.Trilinear, true);
		skybox.setTexture(Skybox.Face.Up, texture);
		skybox.setTexture(Skybox.Face.South, texture);
		backgroundNode.attachChild(skybox);
		_root.attachChild(backgroundNode);

		UIComponent.setUseTransparency(true);
//		SkinManager.setCurrentSkin(new MooSkin());

		hud = new UIHud();
		hud.setupInput(_canvas, _physicalLayer, _logicalLayer);
		camera = _canvas.getCanvasRenderer().getCamera();

		/*
		 * Get the singleton instance of GameManager and initializes it.
		 */
		gameManager.init(this);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ardor3d.example.ExampleBase#updateLogicalLayer(com.ardor3d.util.ReadOnlyTimer)
	 */
	@Override
	protected void updateLogicalLayer(ReadOnlyTimer timer) {
		hud.getLogicalLayer().checkTriggers(timer.getTimePerFrame());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ardor3d.example.ExampleBase#renderGame(com.ardor3d.renderer.Renderer)
	 */
	@Override
	protected void renderGame(Renderer renderer) {
		super.renderGame(renderer);
		renderer.renderBuckets();
		renderer.draw(hud);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.ardor3d.example.ExampleBase#updateGame(com.ardor3d.util.ReadOnlyTimer)
	 */
	@Override
	protected void updateGame(ReadOnlyTimer timer) {
		/*
		 * Rotate the colonies using the rotation matrix.
		 */
		if (gameData.gameState == GameStates.IN_STAR_INFO || gameData.gameState == GameStates.IN_FLEET_INFO) {
			angle1 += timer.getTimePerFrame() * 50; // Update the angle1 using the current tpf to rotate at a constant speed.
			angle1 %= 360; // Wrap the angle1 to keep it inside 0-360 range
			rotate1.fromAngleNormalAxis(angle1 * MathUtils.DEG_TO_RAD, Vector3.UNIT_Z);
			for (StarNode starNode : gameData.starList) {
				if (starNode.getStar().owner != null) {
					starNode.getChild(0).setRotation(rotate1);
				}
			}
			angle2 -= timer.getTimePerFrame() * 50; // Update the angle2 using the current tpf to rotate at a constant speed.
			angle2 %= 360; // Wrap the angle1 to keep it inside 0-360 range
			rotate2.fromAngleNormalAxis(angle2 * MathUtils.DEG_TO_RAD, Vector3.UNIT_Z);
			if (gameData.gameState == GameStates.IN_FLEET_INFO) {
				gameManager.fleetHighlight.setRotation(rotate2);
			} else {
				gameManager.starHighlight.setRotation(rotate2);
			}
		}

		/*
		 * Perform the current action.
		 */
		if (gameData.action != Actions.NONE) {
//			logger.info("(IN) action = " + gameData.action);
//			logger.info("(IN) gameState = " + gameData.gameState);
			gameManager.performAction();
//			logger.info("(OUT) action = " + gameData.action);
//			logger.info("(OUT) gameState = " + gameData.gameState);
		}

		/*
		 * Don't know what this does -- need to investigate
		 */
		control.update(timer.getTimePerFrame()); // required
		hud.updateGeometricState(timer.getTimePerFrame()); // does not seem required

//		final Camera camera = _canvas.getCanvasRenderer().getCamera();
		skybox.setTranslation(camera.getLocation());
		skybox.updateGeometricState(0.0f, true);
	}

	/**
	 * A <code>Spatial</code> was picked. Now determine what to do with the fact.
	 * 
	 * @param target
	 */
	@Override
	protected void processPick(PickData pick, Pickable target) {
		if (gameData.gameState == GameStates.IN_STAR_INFO) {
			if (target instanceof Sphere) {
				Sphere star = (Sphere) target;
				logger.info("Target is a Star: " + star.getName());
				gameData.currentStarNode = (StarNode) star.getParent();
				gameData.action = Actions.STAR_PICKED;
			} else if (target instanceof Quad) {
				logger.info("Target is a Quad: " + ((Quad)target).getName());
				Quad fleetQuad = (Quad) target;
				FleetBannerBillboard fleetBannerBillboard = (FleetBannerBillboard) fleetQuad.getParent();
				logger.info("Target's Parent: " + fleetBannerBillboard.getName());
				gameData.currentFleetNode = (FleetNode) fleetBannerBillboard.getParent();
				logger.info("FleetNode: " + gameData.currentFleetNode.getName());
				gameData.action = Actions.FLEET_PICKED;
				gameData.gameState = GameStates.IN_FLEET_INFO;
			} else if (target instanceof Mesh) {
				logger.info("Target is a Mesh: " + ((Mesh)target).getName());
				Mesh fleetMesh = (Mesh) target;
				Node fleetParentNode = fleetMesh.getParent();
				logger.info("Target's parent Node: " + fleetParentNode.getName());
				if (fleetParentNode instanceof FleetNode) {
					gameData.currentFleetNode = (FleetNode) fleetParentNode;
				} else {
					gameData.currentFleetNode = (FleetNode) fleetParentNode.getParent();
				}
				logger.info("FleetNode: " + gameData.currentFleetNode.getName());
				gameData.action = Actions.FLEET_PICKED;
				gameData.gameState = GameStates.IN_FLEET_INFO;
			} else {
				logger.info("Target is an unknown: " + target);
			}
		} else if (gameData.gameState == GameStates.IN_FLEET_INFO) {
			if (target instanceof Sphere) {
				Sphere star = (Sphere) target;
				logger.info("Target is a Star: " + star.getName());
				StarNode starNode = (StarNode) star.getParent();
				if (starNode != gameData.currentDestination) {
					gameData.currentDestination = starNode;
					gameData.action = Actions.DESTINATION_PICKED;
				}
			} else if (target instanceof Quad) {
				Quad fleetQuad = (Quad) target;
				logger.info("Target is a Fleet Quad: " + fleetQuad.getName());
				FleetBannerBillboard fleetBannerBillboard = (FleetBannerBillboard) fleetQuad.getParent();
				logger.info("Target's Parent: " + fleetBannerBillboard.getName());
				FleetNode fleetNode = (FleetNode) fleetBannerBillboard.getParent();
				logger.info("FleetNode: " + fleetNode.getName());
				if (fleetNode != gameData.currentFleetNode) {
					gameData.currentFleetNode = fleetNode;
					gameData.currentDestination = null;
					gameData.action = Actions.FLEET_PICKED;
				}
			} else if (target instanceof Mesh) {
				Mesh fleetMesh = (Mesh) target;
				logger.info("Target is a Fleet Mesh: " + fleetMesh.getName());
				Node fleetParentNode = fleetMesh.getParent();
				logger.info("Target's parent Node: " + fleetParentNode.getName());
				FleetNode fleetNode;
				if (fleetParentNode instanceof FleetNode) {
					fleetNode = (FleetNode) fleetParentNode;
				} else {
					fleetNode = (FleetNode) fleetParentNode.getParent();
				}
				logger.info("FleetNode: " + fleetNode.getName());
				if (fleetNode != gameData.currentFleetNode) {
					gameData.currentFleetNode = fleetNode;
					gameData.currentDestination = null;
					gameData.action = Actions.FLEET_PICKED;
				}
			} else {
				logger.info("Target is an unknown: " + target);
				gameData.currentDestination = null;
			}
		} else { // Other game states
			logger.info("Target is ignored: " + target);
		}
	}

}
